#include "nand_w25n.h"


#define w25n_spi_cs             nand_spi_cs
#define w25n_spi_mode           nand_spi_mode
#define w25n_spi_wb             nand_spi_wb
#define w25n_spi_rb             nand_spi_rb
#define w25n_spi_cmd_send       nand_spi_cmd_send
#define w25n_spi_read           nand_spi_read
#define w25n_spi_write          nand_spi_write
#define w25n_qpi_read           nand_qpi_read
#define w25n_qpi_write          nand_qpi_write

extern volatile uint16_t nand_u_tick;

uint8_t w25n_wait_ready(void);

/*-------------------------------------------------------------*
* write enable
*--------------------------------------------------------------*/
void w25n_reset(void)
{
	/*slect chip*/
	w25n_spi_cs(NAND_CS_LOW);

	/*switch mode*/
	w25n_spi_mode(NAND_SPI_MODE);

	/*send cmd*/
	w25n_spi_wb(W25N_CMD_RESET);

	/*release chip*/
	w25n_spi_cs(NAND_CS_HIGH);

	w25n_wait_ready();
}

/*-------------------------------------------------------------*
*  featute get
*  read twice to confirm
*--------------------------------------------------------------*/
uint8_t w25n_featute_get(uint8_t addr)
{
	uint8_t st1, st2;

	for (;;)
	{
		/*slect chip*/
		w25n_spi_cs(NAND_CS_LOW);

		/*send cmd*/
		w25n_spi_wb(W25N_CMD_FEATURE_GET);
		w25n_spi_wb(addr);
		st1 = w25n_spi_rb();

		/*release chip*/
		w25n_spi_cs(NAND_CS_HIGH);

		/*slect chip*/
		w25n_spi_cs(NAND_CS_LOW);

		/*send cmd*/
		w25n_spi_wb(W25N_CMD_FEATURE_GET);
		w25n_spi_wb(addr);
		st2 = w25n_spi_rb();

		/*release chip*/
		w25n_spi_cs(NAND_CS_HIGH);

		/*confirm*/
		if (st2 == st1)
			break;
	}

	return st1;
}

/*-------------------------------------------------------------*
*  featute set
*--------------------------------------------------------------*/
void w25n_featute_set(uint8_t addr, uint8_t dat)
{
	/*slect chip*/
	w25n_spi_cs(NAND_CS_LOW);

	/*send cmd*/
	w25n_spi_wb(W25N_CMD_FEATURE_SET);
	w25n_spi_wb(addr);
	w25n_spi_wb(dat);

	/*release chip*/
	w25n_spi_cs(NAND_CS_HIGH);
}

/*-------------------------------------------------------------*
*  wait for chip ready
*--------------------------------------------------------------*/
uint8_t w25n_wait_ready(void)
{
	uint16_t tick_start;

	tick_start = nand_u_tick;

	while (1)
	{
		uint8_t st;
		st = w25n_featute_get(W25N_FEATURE_3_ST);
		if (0 == (st & 01))
		{
			break;
		}

		//对于W25N，最大实际忙时为5ms
		if (nand_u_tick - tick_start > 10)
			return 1;
	}

	return 0;
}

/*-------------------------------------------------------------*
*  wait for chip ready
*--------------------------------------------------------------*/
uint8_t w25n_ecc_error_get(void)
{
	uint8_t st;

	st = w25n_featute_get(W25N_FEATURE_3_ST);
	st &= 0x30;
	//[5:4]=10表示有错误且无法修复
	if ((st == 0x20) || (st == 0x30))
	{
		return 1;
	}

	return 0;
}

/*-------------------------------------------------------------*
* write enable
*--------------------------------------------------------------*/
uint8_t w25n_write_enable(void)
{
	uint8_t set_cnt = 0;
	uint8_t st;

	for (;;)
	{
		/*slect chip*/
		w25n_spi_cs(NAND_CS_LOW);

		/*send cmd*/
		w25n_spi_wb(W25N_CMD_WRITE_ENABLE);

		/*release chip*/
		w25n_spi_cs(NAND_CS_HIGH);

		/*get status*/
		st = w25n_featute_get(W25N_FEATURE_3_ST);

		/*write enable latch judge*/
		if (st & 0x02)
		{
			break;
		}

		set_cnt++;
		if (set_cnt > 3)
		{
			return 1;
		}
	}

	return 0;
}

/*-------------------------------------------------------------*
* write enable
*--------------------------------------------------------------*/
uint8_t w25n_write_disable(void)
{
	uint8_t set_cnt = 0;
	uint8_t st;

	for (;;)
	{
		/*slect chip*/
		w25n_spi_cs(NAND_CS_LOW);

		/*send cmd*/
		w25n_spi_wb(W25N_CMD_WRITE_DISABLE);

		/*release chip*/
		w25n_spi_cs(NAND_CS_HIGH);

		/*get status*/
		st = w25n_featute_get(W25N_FEATURE_3_ST);

		/*write enable latch judge*/
		if (0 == (st & 0x02))
		{
			break;
		}

		set_cnt++;
		if (set_cnt > 3)
		{
			return 1;
		}
	}

	return 0;
}

/*-------------------------------------------------------------*
*  read id
*--------------------------------------------------------------*/
void w25n_read_id(uint8_t *dat)
{
	/*slect chip*/
	w25n_spi_cs(NAND_CS_LOW);

	/*send cmd*/
	w25n_spi_wb(W25N_CMD_READ_ID);
	w25n_spi_wb(0x55);
	*dat++ = w25n_spi_rb(); //Mrf ID
	*dat++ = w25n_spi_rb(); //DEV ID1
	*dat++ = w25n_spi_rb(); //DEV ID2

	/*release chip*/
	w25n_spi_cs(NAND_CS_HIGH);
}


/*-------------------------------------------------------------*
*  read page to cache
*--------------------------------------------------------------*/
uint8_t w25n_read_page(uint32_t addr)
{
	uint8_t buff[8];

	/*slect chip*/
	w25n_spi_cs(NAND_CS_LOW);

	/*send cmd*/
	buff[0] = W25N_CMD_PAGE_READ;
	buff[1] = (addr >> 16) & 0xFF;
	buff[2] = (addr >> 8) & 0xFF;
	buff[3] = (addr >> 0) & 0xFF;
	w25n_spi_cmd_send(buff, 4);

	/*release chip*/
	w25n_spi_cs(NAND_CS_HIGH);

	w25n_wait_ready();

	return 0;
}

/*-------------------------------------------------------------*
*  read page from cache
*--------------------------------------------------------------*/
uint8_t w25n_read_cache(uint16_t addr, uint8_t * dat, uint16_t Len)
{
	uint8_t buff[8];

	/*slect chip*/
	w25n_spi_cs(NAND_CS_LOW);

	/*send cmd*/
	buff[0] = W25N_CMD_CACHE_READ;
	buff[1] = (addr >> 8) & 0xFF;     //addr[15:8]
	buff[2] = (addr >> 0) & 0xFF;     //addr[7:0]
	buff[3] = 0x55;                   //Dummy
	w25n_spi_cmd_send(buff, 4);

	w25n_spi_read(dat, Len);

	/*release chip*/
	w25n_spi_cs(NAND_CS_HIGH);

	return 0;
}

/*-------------------------------------------------------------*
*  read page from cache
*--------------------------------------------------------------*/
uint8_t w25n_q_read_cache(uint16_t addr, uint8_t * dat, uint16_t Len)
{
	uint8_t buff[8];

	/*slect chip*/
	w25n_spi_cs(NAND_CS_LOW);

	/*send cmd*/
	buff[0] = W25N_CMD_CACHE_READ_X4;
	buff[1] = (addr >> 8) & 0xFF;     //addr[15:8]
	buff[2] = (addr >> 0) & 0xFF;     //addr[7:0]
	buff[3] = 0x55;                   //Dummy
	w25n_spi_cmd_send(buff, 4);

	w25n_spi_mode(NAND_QPI_READ_MODE);   //switch to qpi
	w25n_qpi_read(dat, Len);

	/*release chip*/
	w25n_spi_cs(NAND_CS_HIGH);
    
	/*switch mode*/
	w25n_spi_mode(NAND_SPI_MODE);
    
	return 0;
}

/*-------------------------------------------------------------*
*  write page to cache
*--------------------------------------------------------------*/
uint8_t w25n_write_cache(uint16_t addr, uint8_t * dat, uint16_t Len)
{
	uint8_t buff[8];

	w25n_write_enable();

	/*slect chip*/
	w25n_spi_cs(NAND_CS_LOW);

	/*send cmd*/
	buff[0] = W25N_CMD_PROGRAM_LOAD;
	buff[1] = (addr >> 8) & 0xFF;     //addr[15:8]
	buff[2] = (addr >> 0) & 0xFF;     //addr[7:0]
	w25n_spi_cmd_send(buff, 3);

	w25n_spi_write(dat, Len);

	/*release chip*/
	w25n_spi_cs(NAND_CS_HIGH);

	return 0;
}

/*-------------------------------------------------------------*
*  write page to cache
*--------------------------------------------------------------*/
uint8_t w25n_q_write_cache(uint16_t addr, uint8_t * dat, uint16_t Len)
{
	uint8_t buff[8];

	w25n_write_enable();

	/*slect chip*/
	w25n_spi_cs(NAND_CS_LOW);

	/*send cmd*/
	buff[0] = W25N_CMD_PROGRAM_LOAD_X4;
	buff[1] = (addr >> 8) & 0xFF;     //addr[15:8]
	buff[2] = (addr >> 0) & 0xFF;     //addr[7:0]
	w25n_spi_cmd_send(buff, 3);

	w25n_spi_mode(NAND_QPI_WRITE_MODE);  //switch to qpi
	w25n_qpi_write(dat, Len);

	/*release chip*/
	w25n_spi_cs(NAND_CS_HIGH);

	/*switch mode*/
	w25n_spi_mode(NAND_SPI_MODE);
    
	return 0;
}

/*-------------------------------------------------------------*
*  program exe
*--------------------------------------------------------------*/
uint8_t w25n_program_exe(uint32_t addr)
{
	uint8_t buff[8];

	w25n_write_enable();

	/*slect chip*/
	w25n_spi_cs(NAND_CS_LOW);

	/*send cmd*/
	buff[0] = W25N_CMD_PROGRAM_EXE;
	buff[1] = (addr >> 16) & 0xFF;
	buff[2] = (addr >> 8) & 0xFF;
	buff[3] = (addr >> 0) & 0xFF;
	w25n_spi_cmd_send(buff, 4);

	/*release chip*/
	w25n_spi_cs(NAND_CS_HIGH);

	w25n_wait_ready();

	return 0;
}

/*-------------------------------------------------------------*
*  program exe
*--------------------------------------------------------------*/
uint8_t w25n_block_erase(uint32_t block_addr)
{
	uint8_t buff[8];

	uint32_t addr;

	addr = block_addr * W25N_PAGE_PER_BLOCK; //指令需提供页地址

	w25n_write_enable();

	/*slect chip*/
	w25n_spi_cs(NAND_CS_LOW);

	/*send cmd*/
	buff[0] = W25N_CMD_BLOCK_ERASE;
	buff[1] = (addr >> 16) & 0xFF;
	buff[2] = (addr >> 8) & 0xFF;
	buff[3] = (addr >> 0) & 0xFF;
	w25n_spi_cmd_send(buff, 4);

	/*release chip*/
	w25n_spi_cs(NAND_CS_HIGH);

	w25n_wait_ready();

	return 0;
}

/*-------------------------------------------------------------*
*  program exe
*--------------------------------------------------------------*/
uint8_t w25n_chip_erase(void)
{
	uint16_t index;
	for (index = 0; index < W25N_BLOCK_TOTAL; index++)
	{
		w25n_block_erase(index);
	}

	return 0;
}

/*-------------------------------------------------------------*
*  program exe
*--------------------------------------------------------------*/
uint8_t w25n_block_empty_check(uint32_t block_addr)
{
	return 0;
}

/*-------------------------------------------------------------*
*  opt memory select
*--------------------------------------------------------------*/
uint8_t w25n_opt_mem_select(void)
{
	uint8_t st;

	while (1)
	{
		w25n_reset();

		//开启 ECC 和 完整BUFF
		w25n_featute_set(W25N_FEATURE_2_CFG, 0x58);

		st = w25n_featute_get(W25N_FEATURE_2_CFG);

		//最高位 OTP_PRT 不可设置，无需验证
		if ((st & 0x58) == 0x58)
			break;
	}

	return 0;
}

/*-------------------------------------------------------------*
*  main memory select
*--------------------------------------------------------------*/
uint8_t w25n_main_mem_select(void)
{
	uint8_t st;

	while (1)
	{
		w25n_reset();

		//开启 ECC 和 完整BUFF
		w25n_featute_set(W25N_FEATURE_2_CFG, 0x18);

		st = w25n_featute_get(W25N_FEATURE_2_CFG);

		//最高位 OTP_PRT 不可设置，无需验证
		if ((st & 0x58) == 0x18)
			break;
	}

	return 0;
}

/*-------------------------------------------------------------*
*  init
*--------------------------------------------------------------*/
uint8_t w25n_init(void)
{
	uint8_t st;

	while (1)
	{
		w25n_reset();

		w25n_wait_ready();

		st = w25n_featute_get(W25N_FEATURE_2_CFG);

		//最高位 OTP_PRT 不可设置，无需验证
		if ((st & 0x58) == 0x18)
			break;

		//开启 ECC 和 完整BUFF
		w25n_featute_set(W25N_FEATURE_2_CFG, 0x18);
	}

	return 0;
}
